Analysis on the Crowd4SDG Slack included in the Report


library("RColorBrewer") #Color Palette
library("wordcloud") #Wordcloud Library
library("jsonlite") #Handling JSON Files
library("ggplot2") #Plots
library("igraph") #Networks and Analysis
library("scales") #Plot Aesthetics
library("visNetwork") #D3 based Network Plots
library("plotly") #Plots
library("dplyr") #Dataframe Manipulations
library("qgraph") #Network analyis and Plots
library("networkD3") #Sankey

source("custom.R") #Some Custom Functions (courtesy Marc)

users_df = read.csv("data_files/users_an.csv") #Users Meta

channels_df = read.csv("data_files/channels.csv") #Channels Meta
df_emoticon_responses = read.csv("data_files/emoticon_responses.csv")[,-c(1)] #Emoticon Responses
df_text_mentions = read.csv("data_files/text_mentions.csv")[,-c(1)] #Text Mentions
df_texts = read.csv("data_files/texts_channel.csv")[,-c(1)] #Messages
df_channel_join = read.csv("data_files/channel_join.csv")[,-c(1)] #Channel Join Messages
df_threads = read.csv("data_files/threads_channel.csv")[,-c(1)] #Threads

df_th_rep = read.csv("data_files/thread_replies.csv")[,c(-1)] #Thread Replies

teams = c("team-aquatics", "team-floodfinder", "team-toseetocare", "team-safewaterforhomeless", "team-wotter", "team-collaborativewatermonitoring", "teamfloodmaps", "team-filsteiner", "team-mnl", "team-rainwaterharvesting", "team-garudasavior", "team-safeskye", "team-fews", "team-rainwater-collection", "team-potamoi", "team-dailywater", "team_ecolution", "team-thousand-waters", "warbon-footprint")

Plot Functions


#Toggle for arrowheads if directed

visPlot_ni = function(subgraph, communities = rep(1, length(V(subgraph))), nodesize = 10, edgewidth = 0, title="", textsize = nodesize, layout = "layout_nicely", directed = TRUE)
{
  nodes <- data.frame(id = V(subgraph)$name, name = V(subgraph)$print, group = communities)
  nodes$font.size<-textsize + 20
  nodes$font.color = "grey"
  nodes$font.face = "calibri"
  nodes$label = nodes$name
  nodes$value = nodesize
  edges <- data.frame(get.edgelist(subgraph))
  edges$width = edgewidth
  colnames(edges)<-c("from","to","width")
  plt = visNetwork(nodes, edges, height = "1000px", width = "100%",main = title)%>%
    visIgraphLayout(layout = layout) %>%
    visPhysics(solver = "repulsion", hierarchicalRepulsion = "nodeDistance") %>%
    visOptions(highlightNearest = TRUE) %>%
    visNodes(scaling = list(min = 10, max = 30)) %>%
    visEdges(smooth = T, color = list(color = "lightblue", highlight = "blue")) %>% 
    visGroups(groupname = "2", color = list(background = "lightgray",border = "black")) %>%
    visInteraction(keyboard = T,
                   dragNodes = T, 
                   dragView = T, 
                   zoomView = T)
  
  if (directed == TRUE)
    plt = plt %>% visEdges(arrows ="to")
  
  return(plt)
}

#Plot Function with qgraph library
plotNetwork <- function(Gsub, layout='default',maine='', coms=NULL, teams = NULL){
  
  
  if (is.null(coms)){
    com <- cluster_walktrap(Gsub)
    V(Gsub)$color <- com$membership+1
  } else {
    #col = merge(data.frame(team = coms), list)
    V(Gsub)$color <- coms
  }
  vertex.frame.color <- V(Gsub)$color
  
  size <- abs(strength(Gsub, loops=T))
  if (max(size, na.rm=T)>0){ 
    size <- 30 * size / max(size, na.rm=T)
  } else {
    size <- 1
  }
  
  if (is.null(E(Gsub)$weight)){
    width <- 1
    edge.color <- NA 
    l <- layout_with_fr
    
  } else{
    weight <- abs(E(Gsub)$weight)
    width <- .01 + 5 * weight / max(weight, na.rm=T)
    edge.color <- ifelse(E(Gsub)$weight > 0, 'gray', 'blue')
    e <- get.edgelist(Gsub, names=F)
    e <- cbind(e, E(Gsub)$weight)
    l <- qgraph.layout.fruchtermanreingold(e,
                                           vcount=vcount(Gsub),
                                           area=1*(vcount(Gsub)^2),
                                           repulse.rad=5*(vcount(Gsub)^2))
    
  }
  
  if (layout!='default'){
    l <- layout
  }
  
  plot(Gsub,
       layout=l,
       edge.width = width,
       edge.color=edge.color,
       vertex.label = V(Gsub)$Name,
       vertex.label.color = "black",
       vertex.label.cex = 0.7,
       vertex.size = 3 * sqrt(size),
       edge.curved=0.1,
       vertex.frame.color = vertex.frame.color, 
       edge.arrow.width = 0.1,
       edge.arrow.size = 0.1,
#       main=maine,
#       main.cex = 0.3,
       col='gray'
)
  title(main = maine , cex.main = 0.7)
  return(l)
}

Sort Activity by Week


Sys.setenv(TZ="GMT")
df_texts$source_message_timestamp = as.POSIXct(df_texts$source_message_timestamp, origin = "1970-01-01")

df_channel_activity_weekly = data.frame()

for (i in unique(df_texts$channel))
{
  if (T)
  {
    obj = hist(df_texts$source_message_timestamp[df_texts$channel == i], breaks = "weeks", plot = F)
    for (j in 1:length(obj$counts))
    {
      df_channel_activity_weekly = rbind(df_channel_activity_weekly, data.frame(channel = i, activity = obj$counts[j], weeks = obj$breaks[j]))
    }
  }
  
}

Wordclouds


list = sort(unique(df_channel_activity_weekly$weeks))
weeks = list
wc = list()

dir.create('viz', showWarnings = F)


for (i in list[1:(length(list))])
{
  set.seed(1234)
  # print(as.POSIXct(i, origin = "1970-01-01"))
  pdf(paste0('viz/wordclouds_week',match(i,list),'.pdf'), 10, 10)
  # par(mfrow=c(3,3))
   wc = wordcloud(words = df_channel_activity_weekly$channel[df_channel_activity_weekly$weeks == i], 
                  freq = df_channel_activity_weekly$activity[df_channel_activity_weekly$weeks == i], 
                  min.freq = 0,max.words=100, random.order=FALSE, rot.per=0, colors=brewer.pal(8, "Dark2")) 
   dev.off()
}

Timezones

plt = ggplot(users_df, aes(x = reorder(timezone,timezone,
                     function(x)-length(x)))) + 
  geom_bar(stat = "count") + 
  theme_bw() + 
  # theme(axis.text.x = element_text(angle = 90,hjust=0.95,vjust=0.2)) + 
     theme(axis.text.x = element_text(angle = 45,hjust=1,vjust=1)) + 
  ylab("Number of Users") + 
  # scale_y_log10() +
  ggtitle("Users Timezones" )  + 
  theme(legend.position = "none") + 
  xlab("Timezones")
ggplotly(plt)
ggsave('viz/timezones.pdf', width = 7, height = 4)

Channel activity



# tot = aggregate(df_channel_activity_monthly$activity, by = list(channel =df_channel_activity_monthly$channel), FUN = sum)

tb = table(df_texts$channel)
tot = data.frame('channel'=names(tb), x = as.numeric(tb))
# tot$x = tot$x/sum(tot$x)

 plt = ggplot(tot, aes(x=reorder(channel,x), y = x)) + 
   geom_bar(stat = "identity") + theme_bw(base_size = 15) + 
   # theme(axis.text.x = element_text(face = "bold", angle = 90)) + 
     theme(axis.text.x = element_text(angle = 45,hjust=1,vjust=1)) + 
   xlab("") + ylab("Number of Posts") + ggtitle("Total number of posts" )  + theme(legend.position = "none") + coord_flip() 
 #+ scale_y_log10()
 
ggplotly(plt)
ggsave('viz/channels.pdf', width = 7, height = 8)

Country of Origin

Sankey Location


country = read.csv("data_files/participants.csv", stringsAsFactors = FALSE)

nodes = data.frame(Name = union(country$Nationality, country$Team.Name))

links = country[,c("Team.Name", "Nationality")] %>% group_by(Team.Name, Nationality) %>% summarise(count = n())
`summarise()` has grouped output by 'Team.Name'. You can override using the `.groups` argument.
links$IDsource <- match(links$Team.Name, nodes$Name)-1 
links$IDtarget <- match(links$Nationality, nodes$Name)-1

plt = sankeyNetwork(Links = links, Nodes = nodes,
                     Source = "IDsource", Target = "IDtarget",
                     Value = "count", NodeID = "Name", 
                     sinksRight=FALSE, fontSize = 13)
Links is a tbl_df. Converting to a plain data frame.
plt

visSave(plt, 'viz/sankey_nationalities.html')

#ggsave('viz/sankey_nationality.pdf', height=5, width=10)

Sankey Timezone

org_team_part = users_df[!users_df$Team %in% c("Organiser", "Mentor", "Project Partner", "NO IDEA", "External Resource"),]
org_team_part = org_team_part[!grepl("Dropped Out", org_team_part$Team),]

#org_team_part$Team = as.character(org_team_part$Team)

nodes = data.frame(Name = union(org_team_part$Team, org_team_part$timezone))
nodes$Name = as.factor(nodes$Name)


links = org_team_part[,c("Team", "timezone")] %>% group_by(Team, timezone) %>% summarise(count = n())
`summarise()` has grouped output by 'Team'. You can override using the `.groups` argument.
links$IDsource <- match(links$Team, nodes$Name)-1 
links$IDtarget <- match(links$timezone, nodes$Name)-1

plt = sankeyNetwork(Links = links, Nodes = nodes,
                     Source = "IDsource", Target = "IDtarget",
                     Value = "count", NodeID = "Name", 
                     sinksRight=FALSE, fontSize = 13)
Links is a tbl_df. Converting to a plain data frame.
plt

visSave(plt, 'viz/sankey_timezones.html')

#ggsave('viz/sankey_timezones.pdf', height=5, width=10)

No. of Posts + Heatmap (Cut after week 6)

Channel dynamics (Heatmap of Activity)


store = function(a)
{
  if (is.integer(a) & length(a)==0)
    return(0)
  else
    return(a)
}

weeks = sort(unique(df_channel_activity_weekly$weeks))
channels = unique(df_channel_activity_weekly$channel)

channel_act_mat = matrix(0, nrow = length(channels), ncol = length(weeks))


for (i in 1:length(weeks))
{
  for (j in 1:length(channels))
  {
    channel_act_mat[j,i] = store(df_channel_activity_weekly$activity[df_channel_activity_weekly$channel %in% channels[j] & df_channel_activity_weekly$weeks == weeks[i]])
  }
  
}

colnames(channel_act_mat) = as.POSIXct(weeks, origin = "1970-01-01")
rownames(channel_act_mat) = channels
data <- df_channel_activity_weekly
months <- sort(unique(data$weeks))
channels <- unique(data$channel)

M <- matrix(NA,length(channels), length(weeks))

for (i in 1:nrow(data)){
  M[match(data$channel[i], channels), match(data$weeks[i], weeks)] <- data$activity[i]
}

rownames(M) <- channels
colnames(M) <- paste('Week',1:ncol(M))

pdf('viz/channels_heatmap.pdf', 7,10)

peak <- apply(M,1, which.max)
M1 <- M / apply(M,1,max, na.rm=T)

M1[is.na(M1)] <- 0
heatmap.0(M1[order(peak),1:6], cexRow = 1, cexCol = 2, col=colorRampPalette(c("white","darkred")), mar=c(10,15))

dev.off()
null device 
          1 

tot_posts <- apply(M,2,sum, na.rm=T)

pdf('viz/activity_total.pdf', 6,6)
plot.0(tot_posts, type='o', 
       ylab='Number of posts', xlab='Week', lwd=2)
dev.off()
null device 
          1 

Aggregated Mentions and Reaction Networks

#Overall - mentions

text_ment1 = merge(df_text_mentions, users_df[,c("id", "Team")], by.x = "from", by.y = "id")
colnames(text_ment1)[colnames(text_ment1) == "Team"] = "From_Team"

text_ment1 = merge(text_ment1, users_df[,c("id", "Team")], by.x = "to", by.y = "id")
colnames(text_ment1)[colnames(text_ment1) == "Team"] = "To_Team"

g_grouped_mentions = graph_from_data_frame(text_ment1[,c("From_Team", "To_Team", "timestamp", "channel", "from", "to")], directed = TRUE)

E(g_grouped_mentions)$weight = 1

g_grouped_mentions = igraph::simplify(g_grouped_mentions, remove.loops = FALSE, remove.multiple = TRUE)

V(g_grouped_mentions)$print = V(g_grouped_mentions)$name

plt = visPlot_ni(g_grouped_mentions, edgewidth = rescale(E(g_grouped_mentions)$weight, to = c(1,10)), nodesize = degree(g_grouped_mentions, mode = "in"))

write.graph(g_grouped_mentions, "networks/grouped_mentions.graphml", format = "graphml")

plt

visSave(plt, 'networks/network_grouped_mention.html')
#Overall - reactions

reac_ment1 = merge(df_emoticon_responses, users_df[,c("id", "Team")], by.x = "reaction_by", by.y = "id")
colnames(reac_ment1)[colnames(reac_ment1) == "Team"] = "From_Team"

reac_ment1 = merge(reac_ment1, users_df[,c("id", "Team")], by.x = "source_message_from", by.y = "id")
colnames(reac_ment1)[colnames(reac_ment1) == "Team"] = "To_Team"

g_grouped_reactions = graph_from_data_frame(reac_ment1[,c("From_Team", "To_Team", "source_message_timestamp", "channel", "source_message_from", "reaction_by")], directed = TRUE)

E(g_grouped_reactions)$weight = 1

g_grouped_reactions = igraph::simplify(g_grouped_reactions, remove.loops = FALSE, remove.multiple = TRUE)

V(g_grouped_reactions)$print = V(g_grouped_reactions)$name

plt = visPlot_ni(g_grouped_reactions, edgewidth = rescale(E(g_grouped_reactions)$weight, to = c(1,10)), nodesize = degree(g_grouped_reactions, mode = "in"))

write.graph(g_grouped_reactions, "networks/grouped_reactions.graphml", format = "graphml")

plt

visSave(plt, 'networks/network_grouped_reactions.html')

Channel wise interaction networks


library(RColorBrewer)

#using all interactions

pdf('plotTeamNetworks.pdf')
par(mfrow=c(5,4), mar=c(0,0,2,0), cex.main=3)

df_emo = df_emoticon_responses[,c(1,2,3,5)]
colnames(df_emo) = c("From", "To", "Timestamp", "Channel")
df_emo$Type = "emoticon_responses"


df_tmt = df_text_mentions[!df_text_mentions$to %in% c("channel", "here", "everyone"),]
colnames(df_tmt) = c("From", "To", "Timestamp", "Channel")
df_tmt$Type = "text_mentions"

df_total = rbind(df_tmt, df_emo)


net_list = list()
j = 1

#channels = unique(df_total$Channel) 

for (i in teams)
{
  
    subs = df_total[df_total$Channel == i,]
  
    g_temp = graph_from_data_frame(subs, vertices = users_df[,c("id", "Team", "index", "comp_team")])
  
    write.graph(g_temp, paste("channel_nets/", i, ".graphml", sep = ""), format = "graphml")
  
    E(g_temp)$weight = 1
    g_simp_temp = igraph::simplify(g_temp, remove.multiple = TRUE, remove.loops =  FALSE)
  
    g_simp_temp = delete_vertices(g_simp_temp, v = V(g_simp_temp)[degree(g_simp_temp) == 0])
    
    V(g_simp_temp)$Name = V(g_simp_temp)$comp_team
  
    #print(col)
  
    net_list[[j]] = g_simp_temp
    
    j = j + 1
    
    plotNetwork(g_simp_temp, main = i, coms = V(g_simp_temp)$index)
  
}

Density order networks



pdf('plotTeamNetworks_ordered.pdf')
par(mfrow=c(5,4), mar=c(0,0,2,0), cex.main=3)

ordered = order(sapply(net_list, edge_density))

for (i in ordered)
{
  g = net_list[[i]]
  
  plotNetwork(g, main = teams[i], coms = V(g)$index)
  
}

Other Channels


pdf('plotChannelNetworks.pdf')
par(mfrow=c(2,2), mar=c(0,0,2,0), cex.main=3)

net_list = list()
j = 1

for (i in channels_df$name)
{
  if(!i %in% teams & nrow(df_total[df_total$Channel == i,]) > 0)
  {
    subs = df_total[df_total$Channel == i,]
  
    g_temp = graph_from_data_frame(subs, vertices = users_df[,c("id", "Team", "index", "comp_team")])
  
    write.graph(g_temp, paste("channel_nets/", i, ".graphml", sep = ""), format = "graphml")
  
    E(g_temp)$weight = 1
    g_simp_temp = igraph::simplify(g_temp, remove.multiple = TRUE, remove.loops =  FALSE)
  
    g_simp_temp = delete_vertices(g_simp_temp, v = V(g_simp_temp)[degree(g_simp_temp) == 0])
    
    V(g_simp_temp)$Name = V(g_simp_temp)$comp_team
  
    net_list[[j]] = g_simp_temp
    
    j = j + 1
    
    plotNetwork(g_simp_temp, main = i, coms = V(g_simp_temp)$index)
  }
}

Density order networks



pdf('plotChannelNetworks_ordered.pdf')
par(mfrow=c(2,2), mar=c(0,0,2,0), cex.main=3)

ordered = order(sapply(net_list, edge_density))

for (i in ordered)
{
  g = net_list[[i]]
  
  plotNetwork(g, main = teams[i], coms = V(g)$index)
  
}

Interactions with Org. Team


df_org_int = data.frame()

for (i in unique(df_total$Channel))
{
  subs = df_total[df_total$Channel == i,]
  
  part = users_df$id[!grepl("Dropped Out", users_df$Team)]
  
  n = nrow(subs[subs$To %in% users_df$id[users_df$Team %in% c("Mentor", "Organiser", "Project Partner")] & !subs$From %in% users_df$id[users_df$Team %in% c("Mentor", "Organiser", "Project Partner")],])
  
  n1 = nrow(subs[!subs$To %in% users_df$id[users_df$Team %in% c("Mentor", "Organiser", "Project Partner")] & subs$From %in% users_df$id[users_df$Team %in% c("Mentor", "Organiser", "Project Partner")],])
  
  df_org_int = rbind(df_org_int, data.frame(channel = i, messages_org = n+n1))
  
}

Plot


plt = ggplot(df_org_int[df_org_int$channel %in% teams,], aes(x = reorder(channel, messages_org), y = messages_org)) + geom_bar(stat = "identity") + theme_bw(base_size = 20) + xlab("Teams") + ylab("# Interactions with Org Team") + ggtitle("Interactions with Organising Team") + theme(axis.text.x = element_text(angle = 0,hjust=1,vjust=1)) + coord_flip()

ggplotly(plt)

ggsave('viz/interactions_org_team.pdf', width = 8, height = 6)

Camille’s Code

library("gtsummary")

country %>%
  select(Gender, Age) %>%
  tbl_summary(
    statistic = list(all_continuous() ~ "{median} ({min}, {max})",
                     all_categorical() ~ "{n} ({p}%)"))
Characteristic N = 481
Gender
Female 27 (56%)
Male 21 (44%)
Age 22 (15, 38)

1 n (%); Median (Range)


#ggsave('viz/gender_tbl_summary.pdf', width = 6, height = 5)
ggplot(country)+
  aes(x = reorder(Nationality, Nationality, function(x) length(x))) +
  geom_bar(stat="count",width = 0.9, position = position_dodge())+
  scale_y_continuous(limits = c(0, 10), breaks = c(0,2,4,6,8,10))+
  labs(y = "Count",x = "Nationality")+
  theme_bw() + geom_text(stat = "count", aes(label = ""),
            position = position_dodge(width = 0.9), size = 2.5, hjust = -0.5)+
  coord_flip()

ggsave('viz/Nationality.pdf', width = 6, height = 5)


ggplot(country)+
  aes(x = reorder(Country.of.Residence, Country.of.Residence, function(x) length(x))) +
  geom_bar(stat="count",width = 0.9, position = position_dodge())+
  scale_y_continuous(limits = c(0, 10), breaks = c(0,2,4,6,8,10))+
  labs(y = "Count",x = "Country of origin")+
  theme_bw() + geom_text(stat = "count", aes(label = ""),
            position = position_dodge(width = 0.9), size = 2.5, hjust = -0.5)+
  coord_flip()

ggsave('viz/Country_of_origin.pdf', width = 6, height = 5)

Ecolution has one more member than the plot in the report. That is because of one user who listed their own name as the team name. I matched the user with their corresponding team - Ecolution.

ggplot(country)+
  aes(x = reorder(Team.Name, Team.Name, function(x) length(x))) +
  geom_bar(stat="count",width = 0.9, position = position_dodge())+
  scale_y_continuous(limits = c(0, 5), breaks = c(0,1,2,3,4, 5))+
  labs(y = "Count",x = "Teams")+
  theme_bw() + geom_text(stat = "count", aes(label = ""),
            position = position_dodge(width = 0.9), size = 2.5, hjust = -0.5)+
  coord_flip()

ggsave('viz/Team_size.pdf', width = 6, height = 5)


country %>%
  ggplot( aes(x=Gender, y=Age, fill=Gender)) +
  geom_boxplot()+
  scale_fill_brewer()+
  geom_jitter(color="black", size=0.4, alpha=0.9)+
  scale_y_continuous(breaks = c(10, 15, 20, 25, 30, 35, 40, 45))+
  labs(x = "", y="Age", fill = "Gender")+
  theme_bw()

ggsave('viz/Gender_Age.pdf', width = 6, height = 5)

LS0tCnRpdGxlOiAiQ3Jvd2Q0U0RHIFNsYWNrIC0gQW5hbHlzaXMgZm9yIFJlcG9ydCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKQW5hbHlzaXMgb24gdGhlIENyb3dkNFNERyBTbGFjayBpbmNsdWRlZCBpbiB0aGUgUmVwb3J0CgoKYGBge3J9CgpsaWJyYXJ5KCJSQ29sb3JCcmV3ZXIiKSAjQ29sb3IgUGFsZXR0ZQpsaWJyYXJ5KCJ3b3JkY2xvdWQiKSAjV29yZGNsb3VkIExpYnJhcnkKbGlicmFyeSgianNvbmxpdGUiKSAjSGFuZGxpbmcgSlNPTiBGaWxlcwpsaWJyYXJ5KCJnZ3Bsb3QyIikgI1Bsb3RzCmxpYnJhcnkoImlncmFwaCIpICNOZXR3b3JrcyBhbmQgQW5hbHlzaXMKbGlicmFyeSgic2NhbGVzIikgI1Bsb3QgQWVzdGhldGljcwpsaWJyYXJ5KCJ2aXNOZXR3b3JrIikgI0QzIGJhc2VkIE5ldHdvcmsgUGxvdHMKbGlicmFyeSgicGxvdGx5IikgI1Bsb3RzCmxpYnJhcnkoImRwbHlyIikgI0RhdGFmcmFtZSBNYW5pcHVsYXRpb25zCmxpYnJhcnkoInFncmFwaCIpICNOZXR3b3JrIGFuYWx5aXMgYW5kIFBsb3RzCmxpYnJhcnkoIm5ldHdvcmtEMyIpICNTYW5rZXkKCnNvdXJjZSgiY3VzdG9tLlIiKSAjU29tZSBDdXN0b20gRnVuY3Rpb25zIChjb3VydGVzeSBNYXJjKQoKYGBgCgpgYGB7cn0KCnVzZXJzX2RmID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvdXNlcnNfYW4uY3N2IikgI1VzZXJzIE1ldGEKCmNoYW5uZWxzX2RmID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvY2hhbm5lbHMuY3N2IikgI0NoYW5uZWxzIE1ldGEKZGZfZW1vdGljb25fcmVzcG9uc2VzID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvZW1vdGljb25fcmVzcG9uc2VzLmNzdiIpWywtYygxKV0gI0Vtb3RpY29uIFJlc3BvbnNlcwpkZl90ZXh0X21lbnRpb25zID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvdGV4dF9tZW50aW9ucy5jc3YiKVssLWMoMSldICNUZXh0IE1lbnRpb25zCmRmX3RleHRzID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvdGV4dHNfY2hhbm5lbC5jc3YiKVssLWMoMSldICNNZXNzYWdlcwpkZl9jaGFubmVsX2pvaW4gPSByZWFkLmNzdigiZGF0YV9maWxlcy9jaGFubmVsX2pvaW4uY3N2IilbLC1jKDEpXSAjQ2hhbm5lbCBKb2luIE1lc3NhZ2VzCmRmX3RocmVhZHMgPSByZWFkLmNzdigiZGF0YV9maWxlcy90aHJlYWRzX2NoYW5uZWwuY3N2IilbLC1jKDEpXSAjVGhyZWFkcwoKZGZfdGhfcmVwID0gcmVhZC5jc3YoImRhdGFfZmlsZXMvdGhyZWFkX3JlcGxpZXMuY3N2IilbLGMoLTEpXSAjVGhyZWFkIFJlcGxpZXMKCnRlYW1zID0gYygidGVhbS1hcXVhdGljcyIsICJ0ZWFtLWZsb29kZmluZGVyIiwgInRlYW0tdG9zZWV0b2NhcmUiLCAidGVhbS1zYWZld2F0ZXJmb3Job21lbGVzcyIsICJ0ZWFtLXdvdHRlciIsICJ0ZWFtLWNvbGxhYm9yYXRpdmV3YXRlcm1vbml0b3JpbmciLCAidGVhbWZsb29kbWFwcyIsICJ0ZWFtLWZpbHN0ZWluZXIiLCAidGVhbS1tbmwiLCAidGVhbS1yYWlud2F0ZXJoYXJ2ZXN0aW5nIiwgInRlYW0tZ2FydWRhc2F2aW9yIiwgInRlYW0tc2FmZXNreWUiLCAidGVhbS1mZXdzIiwgInRlYW0tcmFpbndhdGVyLWNvbGxlY3Rpb24iLCAidGVhbS1wb3RhbW9pIiwgInRlYW0tZGFpbHl3YXRlciIsICJ0ZWFtX2Vjb2x1dGlvbiIsICJ0ZWFtLXRob3VzYW5kLXdhdGVycyIsICJ3YXJib24tZm9vdHByaW50IikKCgoKYGBgCgpQbG90IEZ1bmN0aW9ucwoKYGBge3J9CgojVG9nZ2xlIGZvciBhcnJvd2hlYWRzIGlmIGRpcmVjdGVkCgp2aXNQbG90X25pID0gZnVuY3Rpb24oc3ViZ3JhcGgsIGNvbW11bml0aWVzID0gcmVwKDEsIGxlbmd0aChWKHN1YmdyYXBoKSkpLCBub2Rlc2l6ZSA9IDEwLCBlZGdld2lkdGggPSAwLCB0aXRsZT0iIiwgdGV4dHNpemUgPSBub2Rlc2l6ZSwgbGF5b3V0ID0gImxheW91dF9uaWNlbHkiLCBkaXJlY3RlZCA9IFRSVUUpCnsKICBub2RlcyA8LSBkYXRhLmZyYW1lKGlkID0gVihzdWJncmFwaCkkbmFtZSwgbmFtZSA9IFYoc3ViZ3JhcGgpJHByaW50LCBncm91cCA9IGNvbW11bml0aWVzKQogIG5vZGVzJGZvbnQuc2l6ZTwtdGV4dHNpemUgKyAyMAogIG5vZGVzJGZvbnQuY29sb3IgPSAiZ3JleSIKICBub2RlcyRmb250LmZhY2UgPSAiY2FsaWJyaSIKICBub2RlcyRsYWJlbCA9IG5vZGVzJG5hbWUKICBub2RlcyR2YWx1ZSA9IG5vZGVzaXplCiAgZWRnZXMgPC0gZGF0YS5mcmFtZShnZXQuZWRnZWxpc3Qoc3ViZ3JhcGgpKQogIGVkZ2VzJHdpZHRoID0gZWRnZXdpZHRoCiAgY29sbmFtZXMoZWRnZXMpPC1jKCJmcm9tIiwidG8iLCJ3aWR0aCIpCiAgcGx0ID0gdmlzTmV0d29yayhub2RlcywgZWRnZXMsIGhlaWdodCA9ICIxMDAwcHgiLCB3aWR0aCA9ICIxMDAlIixtYWluID0gdGl0bGUpJT4lCiAgICB2aXNJZ3JhcGhMYXlvdXQobGF5b3V0ID0gbGF5b3V0KSAlPiUKICAgIHZpc1BoeXNpY3Moc29sdmVyID0gInJlcHVsc2lvbiIsIGhpZXJhcmNoaWNhbFJlcHVsc2lvbiA9ICJub2RlRGlzdGFuY2UiKSAlPiUKICAgIHZpc09wdGlvbnMoaGlnaGxpZ2h0TmVhcmVzdCA9IFRSVUUpICU+JQogICAgdmlzTm9kZXMoc2NhbGluZyA9IGxpc3QobWluID0gMTAsIG1heCA9IDMwKSkgJT4lCiAgICB2aXNFZGdlcyhzbW9vdGggPSBULCBjb2xvciA9IGxpc3QoY29sb3IgPSAibGlnaHRibHVlIiwgaGlnaGxpZ2h0ID0gImJsdWUiKSkgJT4lIAogICAgdmlzR3JvdXBzKGdyb3VwbmFtZSA9ICIyIiwgY29sb3IgPSBsaXN0KGJhY2tncm91bmQgPSAibGlnaHRncmF5Iixib3JkZXIgPSAiYmxhY2siKSkgJT4lCiAgICB2aXNJbnRlcmFjdGlvbihrZXlib2FyZCA9IFQsCiAgICAgICAgICAgICAgICAgICBkcmFnTm9kZXMgPSBULCAKICAgICAgICAgICAgICAgICAgIGRyYWdWaWV3ID0gVCwgCiAgICAgICAgICAgICAgICAgICB6b29tVmlldyA9IFQpCiAgCiAgaWYgKGRpcmVjdGVkID09IFRSVUUpCiAgICBwbHQgPSBwbHQgJT4lIHZpc0VkZ2VzKGFycm93cyA9InRvIikKICAKICByZXR1cm4ocGx0KQp9CgojUGxvdCBGdW5jdGlvbiB3aXRoIHFncmFwaCBsaWJyYXJ5CnBsb3ROZXR3b3JrIDwtIGZ1bmN0aW9uKEdzdWIsIGxheW91dD0nZGVmYXVsdCcsbWFpbmU9JycsIGNvbXM9TlVMTCwgdGVhbXMgPSBOVUxMKXsKICAKICAKICBpZiAoaXMubnVsbChjb21zKSl7CiAgICBjb20gPC0gY2x1c3Rlcl93YWxrdHJhcChHc3ViKQogICAgVihHc3ViKSRjb2xvciA8LSBjb20kbWVtYmVyc2hpcCsxCiAgfSBlbHNlIHsKICAgICNjb2wgPSBtZXJnZShkYXRhLmZyYW1lKHRlYW0gPSBjb21zKSwgbGlzdCkKICAgIFYoR3N1YikkY29sb3IgPC0gY29tcwogIH0KICB2ZXJ0ZXguZnJhbWUuY29sb3IgPC0gVihHc3ViKSRjb2xvcgogIAogIHNpemUgPC0gYWJzKHN0cmVuZ3RoKEdzdWIsIGxvb3BzPVQpKQogIGlmIChtYXgoc2l6ZSwgbmEucm09VCk+MCl7IAogICAgc2l6ZSA8LSAzMCAqIHNpemUgLyBtYXgoc2l6ZSwgbmEucm09VCkKICB9IGVsc2UgewogICAgc2l6ZSA8LSAxCiAgfQogIAogIGlmIChpcy5udWxsKEUoR3N1Yikkd2VpZ2h0KSl7CiAgICB3aWR0aCA8LSAxCiAgICBlZGdlLmNvbG9yIDwtIE5BIAogICAgbCA8LSBsYXlvdXRfd2l0aF9mcgogICAgCiAgfSBlbHNlewogICAgd2VpZ2h0IDwtIGFicyhFKEdzdWIpJHdlaWdodCkKICAgIHdpZHRoIDwtIC4wMSArIDUgKiB3ZWlnaHQgLyBtYXgod2VpZ2h0LCBuYS5ybT1UKQogICAgZWRnZS5jb2xvciA8LSBpZmVsc2UoRShHc3ViKSR3ZWlnaHQgPiAwLCAnZ3JheScsICdibHVlJykKICAgIGUgPC0gZ2V0LmVkZ2VsaXN0KEdzdWIsIG5hbWVzPUYpCiAgICBlIDwtIGNiaW5kKGUsIEUoR3N1Yikkd2VpZ2h0KQogICAgbCA8LSBxZ3JhcGgubGF5b3V0LmZydWNodGVybWFucmVpbmdvbGQoZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZjb3VudD12Y291bnQoR3N1YiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhPTEqKHZjb3VudChHc3ViKV4yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcHVsc2UucmFkPTUqKHZjb3VudChHc3ViKV4yKSkKICAgIAogIH0KICAKICBpZiAobGF5b3V0IT0nZGVmYXVsdCcpewogICAgbCA8LSBsYXlvdXQKICB9CiAgCiAgcGxvdChHc3ViLAogICAgICAgbGF5b3V0PWwsCiAgICAgICBlZGdlLndpZHRoID0gd2lkdGgsCiAgICAgICBlZGdlLmNvbG9yPWVkZ2UuY29sb3IsCiAgICAgICB2ZXJ0ZXgubGFiZWwgPSBWKEdzdWIpJE5hbWUsCiAgICAgICB2ZXJ0ZXgubGFiZWwuY29sb3IgPSAiYmxhY2siLAogICAgICAgdmVydGV4LmxhYmVsLmNleCA9IDAuNywKICAgICAgIHZlcnRleC5zaXplID0gMyAqIHNxcnQoc2l6ZSksCiAgICAgICBlZGdlLmN1cnZlZD0wLjEsCiAgICAgICB2ZXJ0ZXguZnJhbWUuY29sb3IgPSB2ZXJ0ZXguZnJhbWUuY29sb3IsIAogICAgICAgZWRnZS5hcnJvdy53aWR0aCA9IDAuMSwKICAgICAgIGVkZ2UuYXJyb3cuc2l6ZSA9IDAuMSwKIyAgICAgICBtYWluPW1haW5lLAojICAgICAgIG1haW4uY2V4ID0gMC4zLAogICAgICAgY29sPSdncmF5JwopCiAgdGl0bGUobWFpbiA9IG1haW5lICwgY2V4Lm1haW4gPSAwLjcpCiAgcmV0dXJuKGwpCn0KCmBgYAoKIyBTb3J0IEFjdGl2aXR5IGJ5IFdlZWsKCmBgYHtyfQoKU3lzLnNldGVudihUWj0iR01UIikKZGZfdGV4dHMkc291cmNlX21lc3NhZ2VfdGltZXN0YW1wID0gYXMuUE9TSVhjdChkZl90ZXh0cyRzb3VyY2VfbWVzc2FnZV90aW1lc3RhbXAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIikKCmRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5ID0gZGF0YS5mcmFtZSgpCgpmb3IgKGkgaW4gdW5pcXVlKGRmX3RleHRzJGNoYW5uZWwpKQp7CiAgaWYgKFQpCiAgewogICAgb2JqID0gaGlzdChkZl90ZXh0cyRzb3VyY2VfbWVzc2FnZV90aW1lc3RhbXBbZGZfdGV4dHMkY2hhbm5lbCA9PSBpXSwgYnJlYWtzID0gIndlZWtzIiwgcGxvdCA9IEYpCiAgICBmb3IgKGogaW4gMTpsZW5ndGgob2JqJGNvdW50cykpCiAgICB7CiAgICAgIGRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5ID0gcmJpbmQoZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHksIGRhdGEuZnJhbWUoY2hhbm5lbCA9IGksIGFjdGl2aXR5ID0gb2JqJGNvdW50c1tqXSwgd2Vla3MgPSBvYmokYnJlYWtzW2pdKSkKICAgIH0KICB9CiAgCn0KYGBgCgojIFdvcmRjbG91ZHMKCmBgYHtyLGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTR9CgpsaXN0ID0gc29ydCh1bmlxdWUoZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHkkd2Vla3MpKQp3ZWVrcyA9IGxpc3QKd2MgPSBsaXN0KCkKCmRpci5jcmVhdGUoJ3ZpeicsIHNob3dXYXJuaW5ncyA9IEYpCgoKZm9yIChpIGluIGxpc3RbMToobGVuZ3RoKGxpc3QpKV0pCnsKICBzZXQuc2VlZCgxMjM0KQogICMgcHJpbnQoYXMuUE9TSVhjdChpLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpKQogIHBkZihwYXN0ZTAoJ3Zpei93b3JkY2xvdWRzX3dlZWsnLG1hdGNoKGksbGlzdCksJy5wZGYnKSwgMTAsIDEwKQogICMgcGFyKG1mcm93PWMoMywzKSkKICAgd2MgPSB3b3JkY2xvdWQod29yZHMgPSBkZl9jaGFubmVsX2FjdGl2aXR5X3dlZWtseSRjaGFubmVsW2RmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5JHdlZWtzID09IGldLCAKICAgICAgICAgICAgICAgICAgZnJlcSA9IGRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5JGFjdGl2aXR5W2RmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5JHdlZWtzID09IGldLCAKICAgICAgICAgICAgICAgICAgbWluLmZyZXEgPSAwLG1heC53b3Jkcz0xMDAsIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLCBjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSkgCiAgIGRldi5vZmYoKQp9CgpgYGAKCgojIFRpbWV6b25lcyAKCmBgYHtyLCBnaWcud2lkdGg9MywgZmlnLmhlaWdodD0yfQpwbHQgPSBnZ3Bsb3QodXNlcnNfZGYsIGFlcyh4ID0gcmVvcmRlcih0aW1lem9uZSx0aW1lem9uZSwKICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCktbGVuZ3RoKHgpKSkpICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICJjb3VudCIpICsgCiAgdGhlbWVfYncoKSArIAogICMgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCxoanVzdD0wLjk1LHZqdXN0PTAuMikpICsgCiAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSxoanVzdD0xLHZqdXN0PTEpKSArIAogIHlsYWIoIk51bWJlciBvZiBVc2VycyIpICsgCiAgIyBzY2FsZV95X2xvZzEwKCkgKwogIGdndGl0bGUoIlVzZXJzIFRpbWV6b25lcyIgKSAgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgCiAgeGxhYigiVGltZXpvbmVzIikKZ2dwbG90bHkocGx0KQpgYGAKCgpgYGB7ciwgZ2lnLndpZHRoPTMsIGZpZy5oZWlnaHQ9Mn0KZ2dzYXZlKCd2aXovdGltZXpvbmVzLnBkZicsIHdpZHRoID0gNywgaGVpZ2h0ID0gNCkKYGBgCgoKIyBDaGFubmVsIGFjdGl2aXR5CgpgYGB7cn0KCgojIHRvdCA9IGFnZ3JlZ2F0ZShkZl9jaGFubmVsX2FjdGl2aXR5X21vbnRobHkkYWN0aXZpdHksIGJ5ID0gbGlzdChjaGFubmVsID1kZl9jaGFubmVsX2FjdGl2aXR5X21vbnRobHkkY2hhbm5lbCksIEZVTiA9IHN1bSkKCnRiID0gdGFibGUoZGZfdGV4dHMkY2hhbm5lbCkKdG90ID0gZGF0YS5mcmFtZSgnY2hhbm5lbCc9bmFtZXModGIpLCB4ID0gYXMubnVtZXJpYyh0YikpCiMgdG90JHggPSB0b3QkeC9zdW0odG90JHgpCgogcGx0ID0gZ2dwbG90KHRvdCwgYWVzKHg9cmVvcmRlcihjaGFubmVsLHgpLCB5ID0geCkpICsgCiAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE1KSArIAogICAjIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGFuZ2xlID0gOTApKSArIAogICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsaGp1c3Q9MSx2anVzdD0xKSkgKyAKICAgeGxhYigiIikgKyB5bGFiKCJOdW1iZXIgb2YgUG9zdHMiKSArIGdndGl0bGUoIlRvdGFsIG51bWJlciBvZiBwb3N0cyIgKSAgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgY29vcmRfZmxpcCgpIAogIysgc2NhbGVfeV9sb2cxMCgpCiAKZ2dwbG90bHkocGx0KQpgYGAKCgoKYGBge3IsIGdpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTJ9Cmdnc2F2ZSgndml6L2NoYW5uZWxzLnBkZicsIHdpZHRoID0gNywgaGVpZ2h0ID0gOCkKYGBgCgoKQ291bnRyeSBvZiBPcmlnaW4KCgpTYW5rZXkgTG9jYXRpb24KCmBgYHtyfQoKY291bnRyeSA9IHJlYWQuY3N2KCJkYXRhX2ZpbGVzL3BhcnRpY2lwYW50cy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgpub2RlcyA9IGRhdGEuZnJhbWUoTmFtZSA9IHVuaW9uKGNvdW50cnkkTmF0aW9uYWxpdHksIGNvdW50cnkkVGVhbS5OYW1lKSkKCmxpbmtzID0gY291bnRyeVssYygiVGVhbS5OYW1lIiwgIk5hdGlvbmFsaXR5IildICU+JSBncm91cF9ieShUZWFtLk5hbWUsIE5hdGlvbmFsaXR5KSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpKQpsaW5rcyRJRHNvdXJjZSA8LSBtYXRjaChsaW5rcyRUZWFtLk5hbWUsIG5vZGVzJE5hbWUpLTEgCmxpbmtzJElEdGFyZ2V0IDwtIG1hdGNoKGxpbmtzJE5hdGlvbmFsaXR5LCBub2RlcyROYW1lKS0xCgpwbHQgPSBzYW5rZXlOZXR3b3JrKExpbmtzID0gbGlua3MsIE5vZGVzID0gbm9kZXMsCiAgICAgICAgICAgICAgICAgICAgIFNvdXJjZSA9ICJJRHNvdXJjZSIsIFRhcmdldCA9ICJJRHRhcmdldCIsCiAgICAgICAgICAgICAgICAgICAgIFZhbHVlID0gImNvdW50IiwgTm9kZUlEID0gIk5hbWUiLCAKICAgICAgICAgICAgICAgICAgICAgc2lua3NSaWdodD1GQUxTRSwgZm9udFNpemUgPSAxMykKcGx0Cgp2aXNTYXZlKHBsdCwgJ3Zpei9zYW5rZXlfbmF0aW9uYWxpdGllcy5odG1sJykKCiNnZ3NhdmUoJ3Zpei9zYW5rZXlfbmF0aW9uYWxpdHkucGRmJywgaGVpZ2h0PTUsIHdpZHRoPTEwKQpgYGAKClNhbmtleSBUaW1lem9uZQoKYGBge3J9Cm9yZ190ZWFtX3BhcnQgPSB1c2Vyc19kZlshdXNlcnNfZGYkVGVhbSAlaW4lIGMoIk9yZ2FuaXNlciIsICJNZW50b3IiLCAiUHJvamVjdCBQYXJ0bmVyIiwgIk5PIElERUEiLCAiRXh0ZXJuYWwgUmVzb3VyY2UiKSxdCm9yZ190ZWFtX3BhcnQgPSBvcmdfdGVhbV9wYXJ0WyFncmVwbCgiRHJvcHBlZCBPdXQiLCBvcmdfdGVhbV9wYXJ0JFRlYW0pLF0KCiNvcmdfdGVhbV9wYXJ0JFRlYW0gPSBhcy5jaGFyYWN0ZXIob3JnX3RlYW1fcGFydCRUZWFtKQoKbm9kZXMgPSBkYXRhLmZyYW1lKE5hbWUgPSB1bmlvbihvcmdfdGVhbV9wYXJ0JFRlYW0sIG9yZ190ZWFtX3BhcnQkdGltZXpvbmUpKQpub2RlcyROYW1lID0gYXMuZmFjdG9yKG5vZGVzJE5hbWUpCgoKbGlua3MgPSBvcmdfdGVhbV9wYXJ0WyxjKCJUZWFtIiwgInRpbWV6b25lIildICU+JSBncm91cF9ieShUZWFtLCB0aW1lem9uZSkgJT4lIHN1bW1hcmlzZShjb3VudCA9IG4oKSkKbGlua3MkSURzb3VyY2UgPC0gbWF0Y2gobGlua3MkVGVhbSwgbm9kZXMkTmFtZSktMSAKbGlua3MkSUR0YXJnZXQgPC0gbWF0Y2gobGlua3MkdGltZXpvbmUsIG5vZGVzJE5hbWUpLTEKCnBsdCA9IHNhbmtleU5ldHdvcmsoTGlua3MgPSBsaW5rcywgTm9kZXMgPSBub2RlcywKICAgICAgICAgICAgICAgICAgICAgU291cmNlID0gIklEc291cmNlIiwgVGFyZ2V0ID0gIklEdGFyZ2V0IiwKICAgICAgICAgICAgICAgICAgICAgVmFsdWUgPSAiY291bnQiLCBOb2RlSUQgPSAiTmFtZSIsIAogICAgICAgICAgICAgICAgICAgICBzaW5rc1JpZ2h0PUZBTFNFLCBmb250U2l6ZSA9IDEzKQpwbHQKCnZpc1NhdmUocGx0LCAndml6L3NhbmtleV90aW1lem9uZXMuaHRtbCcpCgojZ2dzYXZlKCd2aXovc2Fua2V5X3RpbWV6b25lcy5wZGYnLCBoZWlnaHQ9NSwgd2lkdGg9MTApCgpgYGAKCgoKTm8uIG9mIFBvc3RzICsgSGVhdG1hcCAoQ3V0IGFmdGVyIHdlZWsgNikKCiMgQ2hhbm5lbCBkeW5hbWljcyAoSGVhdG1hcCBvZiBBY3Rpdml0eSkKCmBgYHtyfQoKc3RvcmUgPSBmdW5jdGlvbihhKQp7CiAgaWYgKGlzLmludGVnZXIoYSkgJiBsZW5ndGgoYSk9PTApCiAgICByZXR1cm4oMCkKICBlbHNlCiAgICByZXR1cm4oYSkKfQoKd2Vla3MgPSBzb3J0KHVuaXF1ZShkZl9jaGFubmVsX2FjdGl2aXR5X3dlZWtseSR3ZWVrcykpCmNoYW5uZWxzID0gdW5pcXVlKGRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5JGNoYW5uZWwpCgpjaGFubmVsX2FjdF9tYXQgPSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChjaGFubmVscyksIG5jb2wgPSBsZW5ndGgod2Vla3MpKQoKCmZvciAoaSBpbiAxOmxlbmd0aCh3ZWVrcykpCnsKICBmb3IgKGogaW4gMTpsZW5ndGgoY2hhbm5lbHMpKQogIHsKICAgIGNoYW5uZWxfYWN0X21hdFtqLGldID0gc3RvcmUoZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHkkYWN0aXZpdHlbZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHkkY2hhbm5lbCAlaW4lIGNoYW5uZWxzW2pdICYgZGZfY2hhbm5lbF9hY3Rpdml0eV93ZWVrbHkkd2Vla3MgPT0gd2Vla3NbaV1dKQogIH0KICAKfQoKY29sbmFtZXMoY2hhbm5lbF9hY3RfbWF0KSA9IGFzLlBPU0lYY3Qod2Vla3MsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIikKcm93bmFtZXMoY2hhbm5lbF9hY3RfbWF0KSA9IGNoYW5uZWxzCgoKYGBgCgoKCmBgYHtyfQpkYXRhIDwtIGRmX2NoYW5uZWxfYWN0aXZpdHlfd2Vla2x5Cm1vbnRocyA8LSBzb3J0KHVuaXF1ZShkYXRhJHdlZWtzKSkKY2hhbm5lbHMgPC0gdW5pcXVlKGRhdGEkY2hhbm5lbCkKCk0gPC0gbWF0cml4KE5BLGxlbmd0aChjaGFubmVscyksIGxlbmd0aCh3ZWVrcykpCgpmb3IgKGkgaW4gMTpucm93KGRhdGEpKXsKICBNW21hdGNoKGRhdGEkY2hhbm5lbFtpXSwgY2hhbm5lbHMpLCBtYXRjaChkYXRhJHdlZWtzW2ldLCB3ZWVrcyldIDwtIGRhdGEkYWN0aXZpdHlbaV0KfQoKcm93bmFtZXMoTSkgPC0gY2hhbm5lbHMKY29sbmFtZXMoTSkgPC0gcGFzdGUoJ1dlZWsnLDE6bmNvbChNKSkKCmBgYAoKCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD00fQoKcGRmKCd2aXovY2hhbm5lbHNfaGVhdG1hcC5wZGYnLCA3LDEwKQoKcGVhayA8LSBhcHBseShNLDEsIHdoaWNoLm1heCkKTTEgPC0gTSAvIGFwcGx5KE0sMSxtYXgsIG5hLnJtPVQpCgpNMVtpcy5uYShNMSldIDwtIDAKaGVhdG1hcC4wKE0xW29yZGVyKHBlYWspLDE6Nl0sIGNleFJvdyA9IDEsIGNleENvbCA9IDIsIGNvbD1jb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwiZGFya3JlZCIpKSwgbWFyPWMoMTAsMTUpKQoKZGV2Lm9mZigpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9MywgZmlnLmFzcD0xfQoKdG90X3Bvc3RzIDwtIGFwcGx5KE0sMixzdW0sIG5hLnJtPVQpCgpwZGYoJ3Zpei9hY3Rpdml0eV90b3RhbC5wZGYnLCA2LDYpCnBsb3QuMCh0b3RfcG9zdHMsIHR5cGU9J28nLCAKICAgICAgIHlsYWI9J051bWJlciBvZiBwb3N0cycsIHhsYWI9J1dlZWsnLCBsd2Q9MikKZGV2Lm9mZigpCmBgYAoKQWdncmVnYXRlZCBNZW50aW9ucyBhbmQgUmVhY3Rpb24gTmV0d29ya3MKCmBgYHtyfQojT3ZlcmFsbCAtIG1lbnRpb25zCgp0ZXh0X21lbnQxID0gbWVyZ2UoZGZfdGV4dF9tZW50aW9ucywgdXNlcnNfZGZbLGMoImlkIiwgIlRlYW0iKV0sIGJ5LnggPSAiZnJvbSIsIGJ5LnkgPSAiaWQiKQpjb2xuYW1lcyh0ZXh0X21lbnQxKVtjb2xuYW1lcyh0ZXh0X21lbnQxKSA9PSAiVGVhbSJdID0gIkZyb21fVGVhbSIKCnRleHRfbWVudDEgPSBtZXJnZSh0ZXh0X21lbnQxLCB1c2Vyc19kZlssYygiaWQiLCAiVGVhbSIpXSwgYnkueCA9ICJ0byIsIGJ5LnkgPSAiaWQiKQpjb2xuYW1lcyh0ZXh0X21lbnQxKVtjb2xuYW1lcyh0ZXh0X21lbnQxKSA9PSAiVGVhbSJdID0gIlRvX1RlYW0iCgpnX2dyb3VwZWRfbWVudGlvbnMgPSBncmFwaF9mcm9tX2RhdGFfZnJhbWUodGV4dF9tZW50MVssYygiRnJvbV9UZWFtIiwgIlRvX1RlYW0iLCAidGltZXN0YW1wIiwgImNoYW5uZWwiLCAiZnJvbSIsICJ0byIpXSwgZGlyZWN0ZWQgPSBUUlVFKQoKRShnX2dyb3VwZWRfbWVudGlvbnMpJHdlaWdodCA9IDEKCmdfZ3JvdXBlZF9tZW50aW9ucyA9IGlncmFwaDo6c2ltcGxpZnkoZ19ncm91cGVkX21lbnRpb25zLCByZW1vdmUubG9vcHMgPSBGQUxTRSwgcmVtb3ZlLm11bHRpcGxlID0gVFJVRSkKClYoZ19ncm91cGVkX21lbnRpb25zKSRwcmludCA9IFYoZ19ncm91cGVkX21lbnRpb25zKSRuYW1lCgpwbHQgPSB2aXNQbG90X25pKGdfZ3JvdXBlZF9tZW50aW9ucywgZWRnZXdpZHRoID0gcmVzY2FsZShFKGdfZ3JvdXBlZF9tZW50aW9ucykkd2VpZ2h0LCB0byA9IGMoMSwxMCkpLCBub2Rlc2l6ZSA9IGRlZ3JlZShnX2dyb3VwZWRfbWVudGlvbnMsIG1vZGUgPSAiaW4iKSkKCndyaXRlLmdyYXBoKGdfZ3JvdXBlZF9tZW50aW9ucywgIm5ldHdvcmtzL2dyb3VwZWRfbWVudGlvbnMuZ3JhcGhtbCIsIGZvcm1hdCA9ICJncmFwaG1sIikKCnBsdAoKdmlzU2F2ZShwbHQsICduZXR3b3Jrcy9uZXR3b3JrX2dyb3VwZWRfbWVudGlvbi5odG1sJykKYGBgCgpgYGB7cn0KI092ZXJhbGwgLSByZWFjdGlvbnMKCnJlYWNfbWVudDEgPSBtZXJnZShkZl9lbW90aWNvbl9yZXNwb25zZXMsIHVzZXJzX2RmWyxjKCJpZCIsICJUZWFtIildLCBieS54ID0gInJlYWN0aW9uX2J5IiwgYnkueSA9ICJpZCIpCmNvbG5hbWVzKHJlYWNfbWVudDEpW2NvbG5hbWVzKHJlYWNfbWVudDEpID09ICJUZWFtIl0gPSAiRnJvbV9UZWFtIgoKcmVhY19tZW50MSA9IG1lcmdlKHJlYWNfbWVudDEsIHVzZXJzX2RmWyxjKCJpZCIsICJUZWFtIildLCBieS54ID0gInNvdXJjZV9tZXNzYWdlX2Zyb20iLCBieS55ID0gImlkIikKY29sbmFtZXMocmVhY19tZW50MSlbY29sbmFtZXMocmVhY19tZW50MSkgPT0gIlRlYW0iXSA9ICJUb19UZWFtIgoKZ19ncm91cGVkX3JlYWN0aW9ucyA9IGdyYXBoX2Zyb21fZGF0YV9mcmFtZShyZWFjX21lbnQxWyxjKCJGcm9tX1RlYW0iLCAiVG9fVGVhbSIsICJzb3VyY2VfbWVzc2FnZV90aW1lc3RhbXAiLCAiY2hhbm5lbCIsICJzb3VyY2VfbWVzc2FnZV9mcm9tIiwgInJlYWN0aW9uX2J5IildLCBkaXJlY3RlZCA9IFRSVUUpCgpFKGdfZ3JvdXBlZF9yZWFjdGlvbnMpJHdlaWdodCA9IDEKCmdfZ3JvdXBlZF9yZWFjdGlvbnMgPSBpZ3JhcGg6OnNpbXBsaWZ5KGdfZ3JvdXBlZF9yZWFjdGlvbnMsIHJlbW92ZS5sb29wcyA9IEZBTFNFLCByZW1vdmUubXVsdGlwbGUgPSBUUlVFKQoKVihnX2dyb3VwZWRfcmVhY3Rpb25zKSRwcmludCA9IFYoZ19ncm91cGVkX3JlYWN0aW9ucykkbmFtZQoKcGx0ID0gdmlzUGxvdF9uaShnX2dyb3VwZWRfcmVhY3Rpb25zLCBlZGdld2lkdGggPSByZXNjYWxlKEUoZ19ncm91cGVkX3JlYWN0aW9ucykkd2VpZ2h0LCB0byA9IGMoMSwxMCkpLCBub2Rlc2l6ZSA9IGRlZ3JlZShnX2dyb3VwZWRfcmVhY3Rpb25zLCBtb2RlID0gImluIikpCgp3cml0ZS5ncmFwaChnX2dyb3VwZWRfcmVhY3Rpb25zLCAibmV0d29ya3MvZ3JvdXBlZF9yZWFjdGlvbnMuZ3JhcGhtbCIsIGZvcm1hdCA9ICJncmFwaG1sIikKCnBsdAoKdmlzU2F2ZShwbHQsICduZXR3b3Jrcy9uZXR3b3JrX2dyb3VwZWRfcmVhY3Rpb25zLmh0bWwnKQoKYGBgCgoKQ2hhbm5lbCB3aXNlIGludGVyYWN0aW9uIG5ldHdvcmtzCgpgYGB7cn0KCmxpYnJhcnkoUkNvbG9yQnJld2VyKQoKI3VzaW5nIGFsbCBpbnRlcmFjdGlvbnMKCnBkZigncGxvdFRlYW1OZXR3b3Jrcy5wZGYnKQpwYXIobWZyb3c9Yyg1LDQpLCBtYXI9YygwLDAsMiwwKSwgY2V4Lm1haW49MykKCmRmX2VtbyA9IGRmX2Vtb3RpY29uX3Jlc3BvbnNlc1ssYygxLDIsMyw1KV0KY29sbmFtZXMoZGZfZW1vKSA9IGMoIkZyb20iLCAiVG8iLCAiVGltZXN0YW1wIiwgIkNoYW5uZWwiKQpkZl9lbW8kVHlwZSA9ICJlbW90aWNvbl9yZXNwb25zZXMiCgoKZGZfdG10ID0gZGZfdGV4dF9tZW50aW9uc1shZGZfdGV4dF9tZW50aW9ucyR0byAlaW4lIGMoImNoYW5uZWwiLCAiaGVyZSIsICJldmVyeW9uZSIpLF0KY29sbmFtZXMoZGZfdG10KSA9IGMoIkZyb20iLCAiVG8iLCAiVGltZXN0YW1wIiwgIkNoYW5uZWwiKQpkZl90bXQkVHlwZSA9ICJ0ZXh0X21lbnRpb25zIgoKZGZfdG90YWwgPSByYmluZChkZl90bXQsIGRmX2VtbykKCgpuZXRfbGlzdCA9IGxpc3QoKQpqID0gMQoKI2NoYW5uZWxzID0gdW5pcXVlKGRmX3RvdGFsJENoYW5uZWwpIAoKZm9yIChpIGluIHRlYW1zKQp7CiAgCiAgICBzdWJzID0gZGZfdG90YWxbZGZfdG90YWwkQ2hhbm5lbCA9PSBpLF0KICAKICAgIGdfdGVtcCA9IGdyYXBoX2Zyb21fZGF0YV9mcmFtZShzdWJzLCB2ZXJ0aWNlcyA9IHVzZXJzX2RmWyxjKCJpZCIsICJUZWFtIiwgImluZGV4IiwgImNvbXBfdGVhbSIpXSkKICAKICAgIHdyaXRlLmdyYXBoKGdfdGVtcCwgcGFzdGUoImNoYW5uZWxfbmV0cy8iLCBpLCAiLmdyYXBobWwiLCBzZXAgPSAiIiksIGZvcm1hdCA9ICJncmFwaG1sIikKICAKICAgIEUoZ190ZW1wKSR3ZWlnaHQgPSAxCiAgICBnX3NpbXBfdGVtcCA9IGlncmFwaDo6c2ltcGxpZnkoZ190ZW1wLCByZW1vdmUubXVsdGlwbGUgPSBUUlVFLCByZW1vdmUubG9vcHMgPSAgRkFMU0UpCiAgCiAgICBnX3NpbXBfdGVtcCA9IGRlbGV0ZV92ZXJ0aWNlcyhnX3NpbXBfdGVtcCwgdiA9IFYoZ19zaW1wX3RlbXApW2RlZ3JlZShnX3NpbXBfdGVtcCkgPT0gMF0pCiAgICAKICAgIFYoZ19zaW1wX3RlbXApJE5hbWUgPSBWKGdfc2ltcF90ZW1wKSRjb21wX3RlYW0KICAKICAgICNwcmludChjb2wpCiAgCiAgICBuZXRfbGlzdFtbal1dID0gZ19zaW1wX3RlbXAKICAgIAogICAgaiA9IGogKyAxCiAgICAKICAgIHBsb3ROZXR3b3JrKGdfc2ltcF90ZW1wLCBtYWluID0gaSwgY29tcyA9IFYoZ19zaW1wX3RlbXApJGluZGV4KQogIAp9CgpgYGAKCkRlbnNpdHkgb3JkZXIgbmV0d29ya3MKCmBgYHtyfQoKCnBkZigncGxvdFRlYW1OZXR3b3Jrc19vcmRlcmVkLnBkZicpCnBhcihtZnJvdz1jKDUsNCksIG1hcj1jKDAsMCwyLDApLCBjZXgubWFpbj0zKQoKb3JkZXJlZCA9IG9yZGVyKHNhcHBseShuZXRfbGlzdCwgZWRnZV9kZW5zaXR5KSkKCmZvciAoaSBpbiBvcmRlcmVkKQp7CiAgZyA9IG5ldF9saXN0W1tpXV0KICAKICBwbG90TmV0d29yayhnLCBtYWluID0gdGVhbXNbaV0sIGNvbXMgPSBWKGcpJGluZGV4KQogIAp9CgpgYGAKCk90aGVyIENoYW5uZWxzCgpgYGB7cn0KCnBkZigncGxvdENoYW5uZWxOZXR3b3Jrcy5wZGYnKQpwYXIobWZyb3c9YygyLDIpLCBtYXI9YygwLDAsMiwwKSwgY2V4Lm1haW49MykKCm5ldF9saXN0ID0gbGlzdCgpCmogPSAxCgpmb3IgKGkgaW4gY2hhbm5lbHNfZGYkbmFtZSkKewogIGlmKCFpICVpbiUgdGVhbXMgJiBucm93KGRmX3RvdGFsW2RmX3RvdGFsJENoYW5uZWwgPT0gaSxdKSA+IDApCiAgewogICAgc3VicyA9IGRmX3RvdGFsW2RmX3RvdGFsJENoYW5uZWwgPT0gaSxdCiAgCiAgICBnX3RlbXAgPSBncmFwaF9mcm9tX2RhdGFfZnJhbWUoc3VicywgdmVydGljZXMgPSB1c2Vyc19kZlssYygiaWQiLCAiVGVhbSIsICJpbmRleCIsICJjb21wX3RlYW0iKV0pCiAgCiAgICB3cml0ZS5ncmFwaChnX3RlbXAsIHBhc3RlKCJjaGFubmVsX25ldHMvIiwgaSwgIi5ncmFwaG1sIiwgc2VwID0gIiIpLCBmb3JtYXQgPSAiZ3JhcGhtbCIpCiAgCiAgICBFKGdfdGVtcCkkd2VpZ2h0ID0gMQogICAgZ19zaW1wX3RlbXAgPSBpZ3JhcGg6OnNpbXBsaWZ5KGdfdGVtcCwgcmVtb3ZlLm11bHRpcGxlID0gVFJVRSwgcmVtb3ZlLmxvb3BzID0gIEZBTFNFKQogIAogICAgZ19zaW1wX3RlbXAgPSBkZWxldGVfdmVydGljZXMoZ19zaW1wX3RlbXAsIHYgPSBWKGdfc2ltcF90ZW1wKVtkZWdyZWUoZ19zaW1wX3RlbXApID09IDBdKQogICAgCiAgICBWKGdfc2ltcF90ZW1wKSROYW1lID0gVihnX3NpbXBfdGVtcCkkY29tcF90ZWFtCiAgCiAgICBuZXRfbGlzdFtbal1dID0gZ19zaW1wX3RlbXAKICAgIAogICAgaiA9IGogKyAxCiAgICAKICAgIHBsb3ROZXR3b3JrKGdfc2ltcF90ZW1wLCBtYWluID0gaSwgY29tcyA9IFYoZ19zaW1wX3RlbXApJGluZGV4KQogIH0KfQoKYGBgCgpEZW5zaXR5IG9yZGVyIG5ldHdvcmtzCgpgYGB7cn0KCgpwZGYoJ3Bsb3RDaGFubmVsTmV0d29ya3Nfb3JkZXJlZC5wZGYnKQpwYXIobWZyb3c9YygyLDIpLCBtYXI9YygwLDAsMiwwKSwgY2V4Lm1haW49MykKCm9yZGVyZWQgPSBvcmRlcihzYXBwbHkobmV0X2xpc3QsIGVkZ2VfZGVuc2l0eSkpCgpmb3IgKGkgaW4gb3JkZXJlZCkKewogIGcgPSBuZXRfbGlzdFtbaV1dCiAgCiAgcGxvdE5ldHdvcmsoZywgbWFpbiA9IHRlYW1zW2ldLCBjb21zID0gVihnKSRpbmRleCkKICAKfQoKYGBgCgoKSW50ZXJhY3Rpb25zIHdpdGggT3JnLiBUZWFtCgpgYGB7cn0KCmRmX29yZ19pbnQgPSBkYXRhLmZyYW1lKCkKCmZvciAoaSBpbiB1bmlxdWUoZGZfdG90YWwkQ2hhbm5lbCkpCnsKICBzdWJzID0gZGZfdG90YWxbZGZfdG90YWwkQ2hhbm5lbCA9PSBpLF0KICAKICBwYXJ0ID0gdXNlcnNfZGYkaWRbIWdyZXBsKCJEcm9wcGVkIE91dCIsIHVzZXJzX2RmJFRlYW0pXQogIAogIG4gPSBucm93KHN1YnNbc3VicyRUbyAlaW4lIHVzZXJzX2RmJGlkW3VzZXJzX2RmJFRlYW0gJWluJSBjKCJNZW50b3IiLCAiT3JnYW5pc2VyIiwgIlByb2plY3QgUGFydG5lciIpXSAmICFzdWJzJEZyb20gJWluJSB1c2Vyc19kZiRpZFt1c2Vyc19kZiRUZWFtICVpbiUgYygiTWVudG9yIiwgIk9yZ2FuaXNlciIsICJQcm9qZWN0IFBhcnRuZXIiKV0sXSkKICAKICBuMSA9IG5yb3coc3Vic1shc3VicyRUbyAlaW4lIHVzZXJzX2RmJGlkW3VzZXJzX2RmJFRlYW0gJWluJSBjKCJNZW50b3IiLCAiT3JnYW5pc2VyIiwgIlByb2plY3QgUGFydG5lciIpXSAmIHN1YnMkRnJvbSAlaW4lIHVzZXJzX2RmJGlkW3VzZXJzX2RmJFRlYW0gJWluJSBjKCJNZW50b3IiLCAiT3JnYW5pc2VyIiwgIlByb2plY3QgUGFydG5lciIpXSxdKQogIAogIGRmX29yZ19pbnQgPSByYmluZChkZl9vcmdfaW50LCBkYXRhLmZyYW1lKGNoYW5uZWwgPSBpLCBtZXNzYWdlc19vcmcgPSBuK24xKSkKICAKfQoKYGBgCgpQbG90CgpgYGB7cn0KCnBsdCA9IGdncGxvdChkZl9vcmdfaW50W2RmX29yZ19pbnQkY2hhbm5lbCAlaW4lIHRlYW1zLF0sIGFlcyh4ID0gcmVvcmRlcihjaGFubmVsLCBtZXNzYWdlc19vcmcpLCB5ID0gbWVzc2FnZXNfb3JnKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyB0aGVtZV9idyhiYXNlX3NpemUgPSAyMCkgKyB4bGFiKCJUZWFtcyIpICsgeWxhYigiIyBJbnRlcmFjdGlvbnMgd2l0aCBPcmcgVGVhbSIpICsgZ2d0aXRsZSgiSW50ZXJhY3Rpb25zIHdpdGggT3JnYW5pc2luZyBUZWFtIikgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsaGp1c3Q9MSx2anVzdD0xKSkgKyBjb29yZF9mbGlwKCkKCmdncGxvdGx5KHBsdCkKCmdnc2F2ZSgndml6L2ludGVyYWN0aW9uc19vcmdfdGVhbS5wZGYnLCB3aWR0aCA9IDgsIGhlaWdodCA9IDYpCgpgYGAKCioqKioqKioqKioqKioqKioqKioKCkNhbWlsbGUncyBDb2RlCgpgYGB7cn0KbGlicmFyeSgiZ3RzdW1tYXJ5IikKCmNvdW50cnkgJT4lCiAgc2VsZWN0KEdlbmRlciwgQWdlKSAlPiUKICB0Ymxfc3VtbWFyeSgKICAgIHN0YXRpc3RpYyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+ICJ7bWVkaWFufSAoe21pbn0sIHttYXh9KSIsCiAgICAgICAgICAgICAgICAgICAgIGFsbF9jYXRlZ29yaWNhbCgpIH4gIntufSAoe3B9JSkiKSkKCiNnZ3NhdmUoJ3Zpei9nZW5kZXJfdGJsX3N1bW1hcnkucGRmJywgd2lkdGggPSA2LCBoZWlnaHQgPSA1KQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY291bnRyeSkrCiAgYWVzKHggPSByZW9yZGVyKE5hdGlvbmFsaXR5LCBOYXRpb25hbGl0eSwgZnVuY3Rpb24oeCkgbGVuZ3RoKHgpKSkgKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50Iix3aWR0aCA9IDAuOSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgpKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxMCksIGJyZWFrcyA9IGMoMCwyLDQsNiw4LDEwKSkrCiAgbGFicyh5ID0gIkNvdW50Iix4ID0gIk5hdGlvbmFsaXR5IikrCiAgdGhlbWVfYncoKSArIGdlb21fdGV4dChzdGF0ID0gImNvdW50IiwgYWVzKGxhYmVsID0gIiIpLAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC45KSwgc2l6ZSA9IDIuNSwgaGp1c3QgPSAtMC41KSsKICBjb29yZF9mbGlwKCkKCmdnc2F2ZSgndml6L05hdGlvbmFsaXR5LnBkZicsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNSkKYGBgICAKCmBgYHtyfQoKZ2dwbG90KGNvdW50cnkpKwogIGFlcyh4ID0gcmVvcmRlcihDb3VudHJ5Lm9mLlJlc2lkZW5jZSwgQ291bnRyeS5vZi5SZXNpZGVuY2UsIGZ1bmN0aW9uKHgpIGxlbmd0aCh4KSkpICsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsd2lkdGggPSAwLjksIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMTApLCBicmVha3MgPSBjKDAsMiw0LDYsOCwxMCkpKwogIGxhYnMoeSA9ICJDb3VudCIseCA9ICJDb3VudHJ5IG9mIG9yaWdpbiIpKwogIHRoZW1lX2J3KCkgKyBnZW9tX3RleHQoc3RhdCA9ICJjb3VudCIsIGFlcyhsYWJlbCA9ICIiKSwKICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuOSksIHNpemUgPSAyLjUsIGhqdXN0ID0gLTAuNSkrCiAgY29vcmRfZmxpcCgpCgpnZ3NhdmUoJ3Zpei9Db3VudHJ5X29mX29yaWdpbi5wZGYnLCB3aWR0aCA9IDYsIGhlaWdodCA9IDUpCmBgYAoKRWNvbHV0aW9uIGhhcyBvbmUgbW9yZSBtZW1iZXIgdGhhbiB0aGUgcGxvdCBpbiB0aGUgcmVwb3J0LiBUaGF0IGlzIGJlY2F1c2Ugb2Ygb25lIHVzZXIgd2hvIGxpc3RlZCB0aGVpciBvd24gbmFtZSBhcyB0aGUgdGVhbSBuYW1lLiBJIG1hdGNoZWQgdGhlIHVzZXIgd2l0aCB0aGVpciBjb3JyZXNwb25kaW5nIHRlYW0gLSBFY29sdXRpb24uIAoKYGBge3J9CmdncGxvdChjb3VudHJ5KSsKICBhZXMoeCA9IHJlb3JkZXIoVGVhbS5OYW1lLCBUZWFtLk5hbWUsIGZ1bmN0aW9uKHgpIGxlbmd0aCh4KSkpICsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsd2lkdGggPSAwLjksIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNSksIGJyZWFrcyA9IGMoMCwxLDIsMyw0LCA1KSkrCiAgbGFicyh5ID0gIkNvdW50Iix4ID0gIlRlYW1zIikrCiAgdGhlbWVfYncoKSArIGdlb21fdGV4dChzdGF0ID0gImNvdW50IiwgYWVzKGxhYmVsID0gIiIpLAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC45KSwgc2l6ZSA9IDIuNSwgaGp1c3QgPSAtMC41KSsKICBjb29yZF9mbGlwKCkKCmdnc2F2ZSgndml6L1RlYW1fc2l6ZS5wZGYnLCB3aWR0aCA9IDYsIGhlaWdodCA9IDUpCmBgYAoKYGBge3J9Cgpjb3VudHJ5ICU+JQogIGdncGxvdCggYWVzKHg9R2VuZGVyLCB5PUFnZSwgZmlsbD1HZW5kZXIpKSArCiAgZ2VvbV9ib3hwbG90KCkrCiAgc2NhbGVfZmlsbF9icmV3ZXIoKSsKICBnZW9tX2ppdHRlcihjb2xvcj0iYmxhY2siLCBzaXplPTAuNCwgYWxwaGE9MC45KSsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxMCwgMTUsIDIwLCAyNSwgMzAsIDM1LCA0MCwgNDUpKSsKICBsYWJzKHggPSAiIiwgeT0iQWdlIiwgZmlsbCA9ICJHZW5kZXIiKSsKICB0aGVtZV9idygpCgpnZ3NhdmUoJ3Zpei9HZW5kZXJfQWdlLnBkZicsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNSkKYGBgCgo=